/*
 * This file is part of quickReminder, a software for gamers
 *
 * Copyright (C) 2006-2008 Lucas Romero
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression;
using System.IO;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Net;
using System.Threading;
using HRT;

namespace LucasCode
{
    public class UploadException : Exception
    {
        public UploadException(string msg) : base(msg){}
    };
    [Serializable]
    public class MatchFile : IStaticDataSource
    {
        private static bool m_DoubleCompressFiles = false;
        [XmlIgnore]
        public static bool DoubleCompressFiles
        {
            get { return MatchFile.m_DoubleCompressFiles; }
            set { MatchFile.m_DoubleCompressFiles = value; }
        }
        private static int m_SpeedMeasurementInterval = 500;

        public static int SpeedMeasurementInterval
        {
            get { return MatchFile.m_SpeedMeasurementInterval; }
            set { MatchFile.m_SpeedMeasurementInterval = value; }
        }

        private string m_Comment;
        private DateTime m_LastChanged;
        private DateTime m_DateCreated;
        private DateTime m_DateArchived = DateTime.Now;
        private MatchArchive m_Parent = null;
        public static UploadSetting UploadSettings = new UploadSetting();

        public class UploadSetting
        {
            public int JpegCompression = 80;
        };
        private bool m_AddToArchive;

        [XmlIgnore]
        public bool AddToArchive
        {
          get { return m_AddToArchive; }
          set { m_AddToArchive = value; }
        }



        private string m_TargetName = string.Empty;

        public string TargetName
        {
            get { return m_TargetName; }
            set { m_TargetName = value; }
        }

        [XmlIgnore]
        public MatchFileType FileType
        {
            get
            {
                switch (Path.GetExtension(m_Filename).ToLower())
                {
                    case ".dem":
                        return MatchFileType.Replay;
                    case ".jpg":
                    case ".bmp":
                        return MatchFileType.Screenshot;
                    case ".zip":
                        if (Regex.Match(Path.GetFileNameWithoutExtension(m_Filename), @"\d+").Success)
                            return MatchFileType.Aequitas;
                        return MatchFileType.CompressedContent;
                    default:
                        return MatchFileType.Unknown;
                }
            }
        }

        
        private bool m_WasAutoRenamed = false;

        [XmlIgnore]
        public bool WasAutoRenamed
        {
            get { return m_WasAutoRenamed; }
            set { m_WasAutoRenamed = value; }
        }

        [XmlIgnore]
        public MatchArchive Parent
        {
            get
            {
                return m_Parent;
            }
            set
            {
                m_Parent = value;
            }
        }

        public bool IsArchived
        {
            get
            {
                return m_Parent.FileIsArchived(this.Filename);
            }
        }

        private string m_Filename;

        public MatchFile()
        {
            
        }

        public MatchFile(string filename) : this(filename, Path.GetFileNameWithoutExtension(filename))
        {
            //nothing
        }

        public MatchFile(string filename, string targetname)
        {
            this.m_Filename = filename;
            this.m_TargetName = targetname;
        }

        public string Comment
        {
            get
            {
                return m_Comment;
            }
            set
            {
                m_Comment = value;
            }
        }

        public DateTime LastChanged
        {
            get
            {
                if (IsArchived)
                    return m_LastChanged;
                m_LastChanged = File.GetLastWriteTime(m_Filename);
                return m_LastChanged;
            }
            set
            {
                m_LastChanged = value;
            }
        }

        public DateTime DateCreated
        {
            get
            {
                return m_DateCreated;
            }
            set
            {
                m_DateCreated = value;
            }
        }

        public DateTime DateArchived
        {
            get
            {
                return m_DateArchived;
            }
            set
            {
                m_DateArchived = value;
            }
        }

        public string Filename
        {
            get
            {
                return m_Filename;
            }
            set
            {
                m_Filename = value;
            }
        }

        #region Input Streams

        public Stream GetSource()
        {
            if (!IsArchived)
            {
                return new FileStream(m_Filename, FileMode.Open, FileAccess.Read, FileShare.Read);
            }
            return m_Parent.GetInputStream(this);
        }

        public static string[] CompressedContentExtensions = new string[] { ".zip", ".jpg" };

        public Stream GetUploadStream()
        {
            string ext = Path.GetExtension(m_Filename).ToLower();
            foreach (string s in CompressedContentExtensions)
                if (s.Equals(ext))
                    return GetSource();
            return GetCompressedSource();
        }

        private Stream GetCompressedSource()
        {
            Stream s = null;
            string ext = Path.GetExtension(m_Filename).ToLower();
            switch(ext)
            {
                case ".bmp":
                    s = GetJpegStream(UploadSettings.JpegCompression);
                    break;
                default:
                    s = GetZipStream();
                    break;
            }
            s.Seek(0, SeekOrigin.Begin);
            return s;
        }

        #region Zip Compression

        private const int ZIP_BLOCK_SIZE = 2048;

        private Stream GetZipStream()
        {
            m_CancelUpload = false;
            m_Status = "Compressing";
            MemoryStream m = new MemoryStream();
            int zipbytes;
            long bytecount = 0, lastbytecount = 0, elapsed;
            Byte[] zipbuffer = new Byte[ZIP_BLOCK_SIZE];

            using (Stream s = GetSource())
            {
                ZipOutputStream z = new ZipOutputStream(m);
                z.SetLevel(9);
                ZipEntry entry = new ZipEntry(Path.GetFileName(m_Filename));
                z.PutNextEntry(entry);
                StopWatch watch = new StopWatch();
                while (!m_CancelUpload && ((zipbytes = s.Read(zipbuffer, 0, zipbuffer.Length)) > 0))
                {
                    z.Write(zipbuffer, 0, zipbytes);
                    bytecount += zipbytes;
                    if ((elapsed = watch.Peek()) > MatchFile.m_SpeedMeasurementInterval * 10)
                    {
                        m_Speed = (int)Math.Round(((bytecount - lastbytecount) / (double)elapsed) * 10000);
                        lastbytecount = bytecount;
                        watch.Reset();
                    }
                    UpdateProgress(bytecount, s.Length);
                }
                if (m_CancelUpload)
                    throw new UploadException("Aborted");
                m_Speed = 0;
                z.Finish();
            }
            return m;
        }
        #endregion

        #region BMP Encoding
        private static ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

        private Stream GetJpegStream(int Quality)
        {
            MemoryStream m = new MemoryStream();
            ImageCodecInfo myImageCodecInfo;
            System.Drawing.Imaging.Encoder myEncoder;
            EncoderParameter myEncoderParameter;
            EncoderParameters myEncoderParameters;


            // Get an ImageCodecInfo object that represents the JPEG codec.
            myImageCodecInfo = GetEncoderInfo("image/jpeg");

            // Create an Encoder object based on the GUID

            // for the Quality parameter category.
            myEncoder = System.Drawing.Imaging.Encoder.Quality;

            // Create an EncoderParameters object.

            // An EncoderParameters object has an array of EncoderParameter

            // objects. In this case, there is only one

            // EncoderParameter object in the array.
            myEncoderParameters = new EncoderParameters(1);

            // Save the bitmap as a JPEG file with quality level 25.
            myEncoderParameter = new EncoderParameter(myEncoder, Quality);
            myEncoderParameters.Param[0] = myEncoderParameter;

            using (Stream s = GetSource())
                Image.FromStream(s).Save(m, myImageCodecInfo, myEncoderParameters);
            return m;
        }
        #endregion

        #endregion

        #region Progress reporting
        private void UpdateProgress(double progress)
        {
            UpdateProgress((int)Math.Round(progress));
        }

        private void UpdateProgress(long current, long maximum)
        {
            if (maximum != 0)
                UpdateProgress(((double)current / maximum) * 100);
            else
                UpdateProgress(0);
        }

        private void UpdateProgress(int progress)
        {
            if (progress > 100)
                m_Progress = 100;
            else if (progress < 0)
                m_Progress = 0;
            else
                m_Progress = progress;
            if (m_Parent != null)
                m_Parent.OnFileStatusChanged(this);
        }
        private int m_Progress = 0;
        [XmlIgnore]
        public int Progress
        {
            get
            {
                return m_Progress;
            }
        }

        #endregion

        #region Uploading

        private const UInt32 m_WebUploadBufferSize = 2048;

        public void StartUpload(string cookie)
        {
            m_CancelUpload = false;
            m_Parent.OnUploadStarted(this);
            ThreadPool.QueueUserWorkItem(new WaitCallback(StartUpload), cookie);
        }

        private void StartUpload(object cookieobj)
        {
            string cookie = cookieobj as string;
            string HTML = string.Empty;
            string mFile = string.Empty;
            string targetname = string.Empty;
            try
            {
                if (string.IsNullOrEmpty(m_Parent.Matchlink))
                    throw new UploadException("Missing matchlink!");
                using (Stream data = GetUploadStream())
                {
                    StreamReader html = null;
                    UTF8Encoding encoding = new UTF8Encoding();
                    int bytes;
                    String boundary = "AaB03x";
                    StringBuilder b = new StringBuilder();
                    string content = "";
                    string matchid = m_Parent.ID;
                    b.AppendFormat("--AaB03x\r\nContent-Disposition: form-data; name=\"match\"\r\n\r\n{0}\r\n", matchid);
                    b.Append("--AaB03x\r\nContent-Disposition: form-data; name=\"uploadtype\"\r\n\r\nhttp\r\n");
                    b.Append("--AaB03x\r\nContent-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n104857600\r\n");
                    b.Append("--AaB03x\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n\r\n");
                    b.Append("--AaB03x\r\nContent-Disposition: form-data; name=\"defaulttitle[]\"\r\n\r\nRound 1\r\n");
                    // application/zip oder image/jpeg
                    string type = "", name = "";
                    if (FileType == MatchFileType.Screenshot)
                    {
                        type = "image/jpeg";
                        name = Path.GetFileNameWithoutExtension(m_Filename) + ".jpg";
                    }
                    else
                    {
                        type = "application/zip";
                        name = Path.GetFileNameWithoutExtension(m_Filename) + ".zip";
                    }
                    b.AppendFormat("--AaB03x\r\nContent-Disposition: form-data; name=\"title[]\"\r\n\r\n{0}\r\n--AaB03x\r\nContent-Disposition: file; name=\"datafile[]\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n", m_TargetName, name, type);
                    content = b.ToString();
                    String postsending = "\r\n--AaB03x\r\n";
                    byte[] buffer = new byte[m_WebUploadBufferSize];
                    m_Status = "Uploading";
                    HttpWebRequest r = (HttpWebRequest)WebRequest.Create(m_Parent.Matchlink.Replace("match/" + m_Parent.ID, "matchmedia/savedata"));
                    r.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
                    r.Timeout = Timeout.Infinite;
                    r.KeepAlive = false;
                    r.ProtocolVersion = HttpVersion.Version10;
                    r.CookieContainer = new CookieContainer();
                    r.CookieContainer.Add(new Cookie("COOKIE", (string)cookie, "/", ".esl.eu"));
                    r.Method = "POST";
                    r.ContentType = "multipart/form-data; boundary=AaB03x";
                    r.ContentLength = data.Length + encoding.GetByteCount(content) + encoding.GetByteCount(postsending);
                    using (Stream output = r.GetRequestStream())
                    {
                        int datawritten = 0, lastbytecount = 0;
                        long elapsed;
                        output.Write(encoding.GetBytes(content), 0, encoding.GetByteCount(content));
                        StopWatch watch = new StopWatch();
                        while (!m_CancelUpload && (bytes = data.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            output.Write(buffer, 0, bytes);
                            datawritten += bytes;
                            if ((elapsed = watch.Peek()) > MatchFile.m_SpeedMeasurementInterval * 10)
                            {
                                m_Speed = (int)Math.Round(((datawritten - lastbytecount) / (double)elapsed) * 10000);
                                lastbytecount = datawritten;
                                watch.Reset();
                            }
                            UpdateProgress(datawritten, data.Length);
                        }
                        output.Write(encoding.GetBytes(postsending), 0, encoding.GetByteCount(postsending));
                    }
                    if (m_CancelUpload)
                        throw new UploadException("Aborted");
                    HttpWebResponse response = (HttpWebResponse)r.GetResponse();
                    using (html = new StreamReader(response.GetResponseStream()))
                        HTML = html.ReadToEnd();
                    if (!string.IsNullOrEmpty(HTML))
                    {
                        if (Regex.Match(HTML, @"<a href="".*?/login/""><img.*?src=""http://www.eslgfx.net/gfx/logos/playerphotos/default_small.gif"".*?></a>", RegexOptions.Singleline).Success)
                            throw new UploadException("Wrong login!");

                        if (!Regex.Match(HTML, @"<a.*?href="".*?timezone/.*?"">(\d\d/\d\d/\d\d \d\d:\d\d)h.*?</a>", RegexOptions.Singleline).Success)
                            throw new UploadException("ESL offline!");
                    }
                    else
                        throw new UploadException("Empty response!");
                
                    m_Status = "Complete";
                    m_WasUploaded = true;
                    UpdateProgress(100);
                }
            }
            catch (UploadException ex)
            {
                m_Status = ex.Message;
            }
            catch (WebException ex)
            {
                m_Status = ex.Message;
            }
            catch (IOException ex)
            {
                m_Status = ex.Message;
            }
            finally
            {
                m_Speed = 0;
                m_Parent.OnUploadFinished(this);
            }
        }
        private int m_Speed;

        private string m_Status;

        public string Status
        {
            get { return m_Status; }
            set { m_Status = value; }
        }

        private bool m_WasUploaded;

        public bool WasUploaded
        {
            get { return m_WasUploaded; }
            set { m_WasUploaded = value; }
        }


        public double Speed
        {
            get { return Math.Round((double)m_Speed / 1024, 2); }
        }

        private bool m_CancelUpload;
        public void CancelUpload()
        {
            m_CancelUpload = true;
        }

        #endregion

        public long Size
        {
            get
            {
                long l = 0;
                Stream s = GetSource();
                l = s.Length;
                s.Close();
                return l;
            }
        }
    }
}
